package org.example.im.client.bootstrap;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import lombok.RequiredArgsConstructor;
import org.example.im.client.console.ConsoleCommandFactory;
import org.example.im.client.console.impl.LoginCommand;
import org.example.im.client.pipeline.ClientInitializer;
import org.example.im.common.util.SessionUtil;
import org.springframework.stereotype.Component;

import java.net.InetSocketAddress;
import java.util.Objects;
import java.util.Scanner;
import java.util.concurrent.TimeUnit;

/**
 * @author dingrui
 * @since 2022/2/10
 * @description
 */
@Component
@RequiredArgsConstructor
public class NettyBootstrap {

    private final NioEventLoopGroup group = new NioEventLoopGroup();
    private Channel channel;

    private final ClientInitializer clientInitializer;
    private final LoginCommand loginCommand;
    private final ConsoleCommandFactory consoleCommandFactory;

    private int maxRetryTimes;
    private Bootstrap b;

    public void start(InetSocketAddress address, int remainRetryTimes) {
        try {
            Bootstrap b = new Bootstrap()
                    .group(group)
                    .channel(NioSocketChannel.class)
                    .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5_000)
                    .option(ChannelOption.SO_KEEPALIVE, true)
                    .option(ChannelOption.TCP_NODELAY, true)
                    .remoteAddress(address)
                    .handler(this.clientInitializer);
            this.maxRetryTimes = remainRetryTimes;
            this.b = b;
            ChannelFuture f = this.doConnect(remainRetryTimes); // 尝试重试连接
            if (Objects.isNull(f)) return;
            f.sync();
            this.channel = f.channel();
            f.channel().closeFuture().sync();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            this.group.shutdownGracefully();
        }
    }

    private ChannelFuture doConnect(int remainRetryTimes) {
        if (remainRetryTimes < 0) return null;
        return this.b.connect().addListener(future -> {
            if (future.isSuccess()) {
                System.out.println("[C] 客户端连接服务器[{" + this.b.config().remoteAddress() + "}]成功 启动控制台");
                // 线程
                this.startConsoleThread(((ChannelFuture) future).channel());
            } else if (remainRetryTimes == 0)
                System.out.println("[C] 客户端连接服务器[{" + this.b.config().remoteAddress() + "}]重试次数已经用完 放弃连接");
            else {
                System.out.println("[C] 客户端连接服务器[{" + this.b.config().remoteAddress() + "}]失败 尝试重连 剩余尝试次数[{" + remainRetryTimes + "}]");
                // 第几次重连
                int order = maxRetryTimes - remainRetryTimes + 1;
                // 间隔
                int delay = 1 << order;
                b.config().group().schedule(() -> {
                    this.doConnect(remainRetryTimes - 1);
                }, delay, TimeUnit.SECONDS);
            }
        });
    }

    public void destroy() {
        if (Objects.isNull(this.channel)) return;
        this.channel.close();
        this.group.shutdownGracefully();
    }

    private void startConsoleThread(Channel c) {
        Scanner s = new Scanner(System.in);
        new Thread(() -> {
            while (!Thread.interrupted()) {
                if (!SessionUtil.hasLogin(c)) {
                    this.loginCommand.exec(s, c);
                } else { // other command
                    this.consoleCommandFactory.exec(s, c);
                }
            }
        }).start();
    }
}
